头像框控件

头像框控件

  1. 自定义组合控件,头像框和头像成一定比例,但有个问题,就是没有头像框的时候,会多出来一点间距,这个调整 ui 工作量就大了
  2. 头像框控件固定,间距也固定,头像框就盖在头像上面,通过 clipChildren 来控制绘制到父控件中

Mashi 头像框控件

  1. 支持 webp 静态图
  2. 支持 svga 动态图
// 头像:头像框 150:198
open class AvatarBoxView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val ivAvatar: BaseImageView by lazy { findViewById<BaseImageView>(R.id.iv_avatar) }
    private val ivAvatarBoxStatic: BaseImageView by lazy { findViewById<BaseImageView>(R.id.iv_avatar_box_static) }
    private val ivAvatarBoxSVGA: CommonSVGAView by lazy { findViewById<CommonSVGAView>(R.id.iv_avatar_box_svga) }
    private var goodsDetail: StoreGoodsDetail? = null
    private var tag = "default"
    private var clipCount = 1

    companion object {
        private const val TAG = StoreConstants.TAG
    }

    init {
        LayoutInflater.from(context).inflate(R.layout.view_avatar_layout, this, true)
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AvatarBoxView)
        try {
            clipCount = typedArray?.getInt(R.styleable.AvatarBoxView_clip_count, 1) ?: 1
        } finally {
            typedArray?.recycle()
        }
    }

    fun getAvatarBoxSVGA(): SVGAImageView? {
        if (goodsDetail == null || goodsDetail?.isStaticPreview() != false) {
            return null
        }
        return ivAvatarBoxSVGA
    }

    fun isAvatarBoxSVGAShow(): Boolean {
        val avatarBoxSVGA = getAvatarBoxSVGA()
        return avatarBoxSVGA != null && avatarBoxSVGA.drawable != null
    }

    fun showAvatarBoxSVGADirect() {
        getAvatarBoxSVGA()?.startAnimation()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        // LogUtils.d(TAG, "onMeasure widthSize=$widthSize, heightSize=$heightSize")
        if (widthSize == 0 && heightSize == 0) {
            // If there are no constraints on size, let AvatarView measure
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)

            // Now use the smallest of the measured dimensions for both dimensions
            val minSize = measuredWidth.coerceAtMost(measuredHeight)
            setMeasuredDimension(minSize, minSize)
            // LogUtils.w(TAG, "onMeasure minSize=$minSize")
            return
        }

        val size: Int
        size = if (widthSize == 0 || heightSize == 0) {
            // If one of the dimensions has no restriction on size, set both dimensions to be the
            // on that does
            widthSize.coerceAtLeast(heightSize)
            // LogUtils.w(TAG, "onMeasure(widthSize == 0 || heightSize == 0) size=$size")
        } else {
            // Both dimensions have restrictions on size, set both dimensions to be the
            // smallest of the two
            widthSize.coerceAtMost(heightSize)
            // LogUtils.w(TAG, "onMeasure(widthSize and heightSize != 0) size=$size")
        }

        val newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
        super.onMeasure(newMeasureSpec, newMeasureSpec)
    }


    override fun measureChildWithMargins(child: View?, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int) {
        val widthSize = MeasureSpec.getSize(parentWidthMeasureSpec)
        val heightSize = MeasureSpec.getSize(parentHeightMeasureSpec)
        if (child?.id == R.id.iv_avatar_box_static || child?.id == R.id.iv_avatar_box_svga) {
            // LogUtils.d(TAG, "measureChildWithMargins(R.id.iv_avatar) widthSize=$widthSize, heightSize=$heightSize child=$child")
            val size: Int
            if (widthSize == 0 || heightSize == 0) {
                // If one of the dimensions has no restriction on size, set both dimensions to be the
                // on that does
                size = widthSize.coerceAtLeast(heightSize)
                // LogUtils.w(TAG, "measureChildWithMargins(widthSize == 0 || heightSize == 0) size=$size")
            } else {
                // Both dimensions have restrictions on size, set both dimensions to be the
                // smallest of the two
                size = widthSize.coerceAtMost(heightSize)
                //LogUtils.w(TAG, "measureChildWithMargins(widthSize and heightSize != 0) size=$size")
            }
            val avatarSize = size * (198.0 / 150.0) // FIXME:ZFS 2020年03月03日20:07:53 改成可配置?
            // LogUtils.w(TAG, "measureChildWithMargins final size avatarSize=$avatarSize(${avatarSize.toInt()})")
            val newMeasureSpec = MeasureSpec.makeMeasureSpec(avatarSize.toInt(), MeasureSpec.EXACTLY)
            super.measureChildWithMargins(child, newMeasureSpec, widthUsed, newMeasureSpec, heightUsed)
        } else {
            // LogUtils.d(TAG, "measureChildWithMargins widthSize=$widthSize, heightSize=$heightSize,widthUsed=$widthUsed, heightUsed=$heightUsed, child=$child")
            super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed)
        }
    }

    fun tag(tag: String): AvatarBoxView {
        this.tag = tag
        return this
    }

    fun showAvatarAndBox(user: User?): AvatarBoxView {
        showAvatar(user).showAvatarBox(user?.avatarBox)
        return this
    }

    fun showAvatar(user: User?): AvatarBoxView {
        ivAvatar.visible().showAvatar(user?.avatar ?: "")
        return this
    }

    fun showAvatarBox(storeGoodsDetail: StoreGoodsDetail?): AvatarBoxView {
        this.goodsDetail = storeGoodsDetail
        if (storeGoodsDetail?.previewPic == null) {
            hideAvatarBox()
        } else {
            storeGoodsDetail.previewPic?.apply {
                if (isStatic) {
                    // LogUtils.d(TAG, "${anchor("showAvatarBox")}静态头像框,picUrl=$picUrl")
//                ivAvatarBoxStatic.visible().showAvatar(picUrl ?: "")
                    ivAvatarBoxStatic.setImageURI("")
                    ivAvatarBoxSVGA.gone()
                    ImageLoaderManager.createImageOptions(ivAvatarBoxStatic.visible(), getPreviewPicUrl(storeGoodsDetail)
                            ?: "")
                            .setOnLoaderResultCallBack(object : OnLoaderResultCallBack {
                                override fun onSucc(animatable: Animatable?) {
                                }

                                override fun onFail(throwable: Throwable?) {
                                    LogUtils.printStackTrace(throwable)
                                    LogUtils.w(TAG, "${this@AvatarBoxView.anchor("showAvatarBox")}[$tag]静态头像框加载失败(${throwable?.message}),picUrl=$picUrl", true)
                                }
                            })
                            .show()
                } else {
                    val pic = getPreviewPicUrl(storeGoodsDetail) ?: ""
                    ivAvatarBoxStatic.setImageURI("")
                    ivAvatarBoxSVGA.stop()
                    ivAvatarBoxSVGA.tag = pic
                    ivAvatarBoxSVGA.visible()
                            .loop()
                            .setCallback(object : CommonSVGAView.ParseCallback {
                                override fun onError() {
                                    LogUtils.w(TAG, "${this@AvatarBoxView.anchor("showAvatarBox")}[$tag]动态SVGA头像框加载失败,picUrl=$picUrl", true)
                                }

                            })
                            .show(pic)
                }
            }
        }
        return this
    }

    // 隐藏头像框
    fun hideAvatarBox() {
        LogUtils.w(TAG, "${anchor("hideAvatarBox")}[$tag]隐藏头像框静态/SVGA,previewPic=${goodsDetail?.previewPic}", true)
        ivAvatarBoxStatic.gone()
        ivAvatarBoxSVGA.gone()
    }

    override fun dispatchDraw(canvas: Canvas?) {
        clip(this)
        super.dispatchDraw(canvas)
    }

    private fun clip(v: ViewGroup?) {
        if (v == null || clipCount < 0) {
            return
        }
        v.clipChildrenNot().clipPaddingNot()
//        LogUtils.i(TAG, "${anchor("dispatchDraw")}[$tag] clipCount=$clipCount, parent_id=${v.id},parent clipChildren & clipToPadding = false,parent=$v")
        while (clipCount >= 0) {
            clipCount--
            clip(v.parent as? ViewGroup)
        }
    }

}

糗百静态图头像框

在 ImageView 的 onDraw,通过 canvas translate 到父控件外,再通过 clipChildren 来实现

public class VHeadSimpleDrawee extends QBThemeImageView {
    /**
     * 大v图标的大小跟头像本身大小比例是 0.35 找iOS同学确认的,最好在初始化的时候写进去
     */
    public static final float SCALE = 0.35f;
    private Drawable mBottomDrawable = null;
    private int drawableWidth;
    private int drawableHeight;
    private int defValue = 0;
    private DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;

    public VHeadSimpleDrawee(Context context, GenericDraweeHierarchy hierarchy) {
        super(context, hierarchy);
        init(null);
    }


    public VHeadSimpleDrawee(Context context) {
        super(context);
        init(null);
    }

    public VHeadSimpleDrawee(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public VHeadSimpleDrawee(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    public VHeadSimpleDrawee(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    private void init(AttributeSet attributeSet) {
        defValue = getContext().getResources().getDimensionPixelSize(R.dimen.qb_px_15);
        TypedArray a = getContext().obtainStyledAttributes(attributeSet, R.styleable.VHeadSimpleDrawee);
        if (a != null) {
            try {
                drawableHeight = a.getDimensionPixelSize(R.styleable.VHeadSimpleDrawee_drawable_height, defValue);
                drawableWidth = a.getDimensionPixelSize(R.styleable.VHeadSimpleDrawee_drawable_width, defValue);
            } finally {
                a.recycle();
            }
        }

        GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources()).build();
        mDraweeHolder = DraweeHolder.create(hierarchy, getContext());
        mDraweeHolder.getTopLevelDrawable().setCallback(this);
    }

    public void setDrawableSize(int width, int height) {
        drawableHeight = height;
        drawableWidth = width;
        invalidate();
    }

    public void showVImage(List<TalentBean> talentBeans) {
        showVImage((talentBeans != null && talentBeans.size() > 0) ? talentBeans.get(0) : null);
    }

    private void showVImage(TalentBean talent) {
        if (talent != null) {
            mBottomDrawable =  getResources().getDrawable(getResFromTalent(talent));
        } else {
            mBottomDrawable = null;
        }
        invalidate();
    }

    public static int getResFromTalent(TalentBean talent) {
        if (talent == null) {
            return 0;
        }
        if (talent.cmd < 0) {
            return UIHelper.isNightTheme() ? R.drawable.im_ic_certification_night : R.drawable.im_ic_certification;
        } else {
            return UIHelper.isNightTheme() ?R.drawable.icon_head_v_night : R.drawable.icon_head_v;
        }
    }

    public void photoFrame(String photoFrame) {
        DraweeController controller;
        Uri uri = Uri.EMPTY;
        try {
            if (!TextUtils.isEmpty(photoFrame)) {
                uri = Uri.parse(photoFrame);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (mDraweeHolder != null) {
            controller = Fresco.newDraweeControllerBuilder()
                    .setUri(uri)
                    .setOldController(mDraweeHolder.getController())
                    .build();
            mDraweeHolder.setController(controller);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        ViewGroup parent = (ViewGroup) getParent();
        if (parent != null) {
            parent.setClipChildren(false);
            parent.setClipToPadding(false);
        }

        if (getMeasuredWidth() > 0 && mDraweeHolder != null && mDraweeHolder.getTopLevelDrawable() != null) {
            int width = getMeasuredWidth();
            int photoFrameSize = (int) (width * 1.4f);
            canvas.save();
            canvas.translate(-(photoFrameSize - width)/2, -(photoFrameSize - width)/2);
            Drawable drawable = mDraweeHolder.getTopLevelDrawable();
            drawable.setBounds(new Rect(0, 0, photoFrameSize, photoFrameSize));
            drawable.draw(canvas);
            canvas.restore();
        }

        int imgWidth = getMeasuredWidth();
        int imgHeight = getMeasuredHeight();
        if (imgHeight > 0 && imgWidth > 0) {
            drawableHeight = (int) (imgHeight * (SCALE));
            drawableWidth = (int) (imgWidth * (SCALE));
        }
        if (mBottomDrawable != null) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            int left = width - drawableWidth;
            int top = height - drawableHeight;
            mBottomDrawable.setColorFilter(getColorFilter());
            mBottomDrawable.setBounds(
                    left,
                    top,
                    width,
                    height);
            mBottomDrawable.draw(canvas);
        }
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mDraweeHolder != null) {
            mDraweeHolder.onDetach();
        }
    }

    @Override
    public void onStartTemporaryDetach() {
        super.onStartTemporaryDetach();
        if (mDraweeHolder != null) {
            mDraweeHolder.onDetach();
        }

    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mDraweeHolder != null) {
            mDraweeHolder.onAttach();
        }

    }

    @Override
    public void onFinishTemporaryDetach() {
        super.onFinishTemporaryDetach();
        if (mDraweeHolder != null) {
            mDraweeHolder.onAttach();
        }

    }

    @Override
    protected boolean verifyDrawable(@NonNull Drawable dr) {
        if (mDraweeHolder != null && dr == mDraweeHolder.getTopLevelDrawable()) {
            return true;
        }
        return super.verifyDrawable(dr);
    }
}